package org.testis.testdriver.validation;

import org.testis.testdriver.*;
import java.util.Collection;
import java.util.Iterator;
import org.testis.data.TestStepDataObject;
import org.testis.data.TestStepResultDataObject;
import org.testis.data.TestStepResultType;
import org.testis.services.TestStepExecutor;
import org.testis.testdriver.AtofEnumaration.AtofLogLevel;
import org.testis.testdriver.exceptions.ImpossibleToAchieveSuccessPercentageException;
import org.testis.testdriver.resultsmatrix.ThreeDResultsMatrix;
import com.mallardsoft.tuple.Pair;
import com.mallardsoft.tuple.Tuple;
import java.util.List;
import java.util.Map;

/**
 * This class is the base class of the TestPart Validation Startegy Design Pattern.
 * 
 * @author barisergun
 */
public abstract class TestPartValidateStrategy {

    protected TestPart testPartInRun;

    public TestPartValidateStrategy(TestPart inUse) {
        testPartInRun = inUse;
    }

    public boolean validateFinalPartResult(TestIterator testIterInRun,
            AtofEnumaration.DebuggingMode mode,
            boolean partResult) {
        StepContainer caseContainer = (StepContainer) testPartInRun;
        String partParentGuid = caseContainer.getPartGuid();
        String partParentName = caseContainer.getPartName();
        String partParentParentGuid = caseContainer.getParent().getPartGuid();

        if (caseContainer.getTestPartList().isEmpty()) { // This means had no childs so reflect the latest result
            // as it was
            caseContainer.setTestPartResult(caseContainer.getTestPartResult());
            Byte res = 2; // Because we log this when there is no childs.

            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                    partParentName,
                    AtofLogLevel.INFO,
                    TestStringConstants.getEmptyTestContainerString(),
                    partParentGuid);

            NotificationSubjectSingleton.getInstance().notifyLogHeaderMessages(
                    Integer.parseInt(
                    caseContainer.getPartId()),
                    caseContainer.getContainerType().ordinal(),
                    res,
                    partParentParentGuid,
                    partParentName,
                    partParentGuid,
                    ExecutionGuidProvider.createInstance().
                    getExistingExecId());

        } else { // if (caseContainer.getTestPartList().isEmpty())
            if (!testPartInRun.getFailedConditions().isEmpty()) {
                caseContainer.setTestPartResult(caseContainer.getTestPartResult());
                Byte res = 3;  // log 3 when condition fails

                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        partParentName,
                        AtofLogLevel.INFO,
                        TestStringConstants.getConditionFailedString() + "\n"
                        + testPartInRun.getFailedConditions().toString(),
                        partParentGuid);

                NotificationSubjectSingleton.getInstance().notifyLogHeaderMessages(
                        Integer.parseInt(
                        caseContainer.getPartId()),
                        caseContainer.getContainerType().ordinal(),
                        res,
                        partParentParentGuid,
                        partParentName,
                        partParentGuid,
                        ExecutionGuidProvider.createInstance().
                        getExistingExecId());
            } else { // if (!conditionPass) 
                validateFinalMatrix(caseContainer);
            }
        }
        caseContainer.resetCaseIndexes();


        NotificationSubjectSingleton.getInstance().notifyLogMessages(
                partParentName,
                AtofLogLevel.INFO,
                TestStringConstants.getEndedExecutionString(),
                partParentGuid);

        boolean continueNextCase =
                driveValidationResult(testPartInRun,
                testIterInRun,
                mode);

        if (continueNextCase) {
            return validateIfToContinue(partResult);
        }
        return true;
    }

    protected void validateFinalMatrix(StepContainer refCur) {

        boolean finalResult = true;

        finalResult = refCur.validateThreeDResultArray();
        if (refCur.getContainerType()
                == AtofEnumaration.TestPartType.TestStepGroup) {
            // In case it was a TestStepGroup Result it must be
            // added to the parent Test Case result set for future validation
            Pair<TestStepDataObject, Boolean> resPair =
                    new Pair<TestStepDataObject, Boolean>(null,
                    finalResult);
            StepContainer curParent = (StepContainer) refCur.getParent();
            refCur.getCaseResultSet().put(curParent.getTestCaseDataIndex(),
                    resPair);

            ThreeDResultsMatrix ourParentMatrix =
                    curParent.getResultMatrix();

            ourParentMatrix.setResultIn3dArray(curParent.getInvocationCountIndex(),
                    curParent.getTestPartList().
                    indexOf(refCur),
                    curParent.getTestCaseDataIndex(),
                    finalResult);


        }
        refCur.setTestPartResult(finalResult);
    }

    /**
     * This method is used to validate Test Part result according various Test Part
     * types strategies. A Test case childs results will be validated to a Test Case
     * according to TestCase validation strategy and TestCase Group childs run results 
     * will be validated according to Test Case Group validation strategy etc..
     * 
     * @param testIterInRun reference to the current TestIterator 
     * @param mode debugging mode. 
     * @param partResult boolean result of the last Test Part run operation. 
     * @return boolean true if the Final validation must take place
     */
    public boolean validatePartResult(TestIterator testIterInRun,
            AtofEnumaration.DebuggingMode mode,
            boolean partResult) {

        StepContainer caseContainer = (StepContainer) testPartInRun;
        waitAsyncSteps(caseContainer);

        boolean finalValidation = false;
        boolean impossibleSuccess = false;

        String logPartNameForIteration = caseContainer.getPartNameLongFromContainer();
        String logPartGuidForIteration = caseContainer.getPartGuidLongFromContainer();
        String logPartNameForMainContainer = caseContainer.getPartName();
        String logPartGuidForMainContainer = caseContainer.getPartGuid();

        try {
            caseContainer.validateCurrentThreeDResultArray(caseContainer.getActivateImpossibleSuccessPercFeature());
        } catch (ImpossibleToAchieveSuccessPercentageException ex) {
            impossibleSuccess = true;
        }
        if (!impossibleSuccess) {
            if (caseContainer.getRetryCaseWithOtherData() == true) {
                caseContainer.setRetryCaseWithOtherData(false);
                testIterInRun.previous();
                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        logPartNameForMainContainer,
                        AtofLogLevel.INFO,
                        TestStringConstants.getTestPartIteratedBackForDataString(),
                        logPartGuidForMainContainer);
                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        logPartNameForIteration,
                        AtofLogLevel.INFO,
                        TestStringConstants.getEndedExecutionString(),
                        logPartGuidForIteration);
            } else {

                if (caseContainer.getTestPartList().isEmpty()) {

                    finalValidation = true;

                } else {

                    NotificationSubjectSingleton.getInstance().notifyLogMessages(
                            logPartNameForIteration,
                            AtofLogLevel.INFO,
                            TestStringConstants.getValidatingChildTestStepsString(),
                            logPartGuidForIteration);

                    validateTestCase(caseContainer);

                    NotificationSubjectSingleton.getInstance().notifyLogMessages(
                            logPartNameForIteration,
                            AtofLogLevel.INFO,
                            TestStringConstants.getEndedExecutionString(),
                            logPartGuidForIteration);

                    caseContainer.setInvocationCountIndex(caseContainer.getInvocationCountIndex() + 1);

                    if (caseContainer.getInvocationCountIndex() < caseContainer.getInvocationCount()) {
                        testIterInRun.previous();
                        NotificationSubjectSingleton.getInstance().notifyLogMessages(
                                logPartNameForMainContainer,
                                AtofLogLevel.INFO,
                                TestStringConstants.getTestPartIteratedBackForInvocationString(),
                                logPartGuidForMainContainer);



                    } else {
                        finalValidation = true;
                    }
                }

            }
        } else {

            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                    logPartNameForIteration,
                    AtofLogLevel.INFO,
                    TestStringConstants.getValidatingChildTestStepsString(),
                    logPartGuidForIteration);

            validateTestCase(caseContainer);

            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                    logPartNameForIteration,
                    AtofLogLevel.INFO,
                    TestStringConstants.getEndedExecutionString(),
                    logPartGuidForIteration);

            finalValidation = true;
            caseContainer.setRetryCaseWithOtherData(false);
            caseContainer.setInvocationCountIndex(caseContainer.getInvocationCountIndex() + 1);
        }


        return finalValidation;

    }

    /**
     * This method is used to call the desired debugging mode Test Case
     * validation from a Factory Design Pattern.
     *
     * @param  curPart  TestPart that is being executed.
     * @param  innerIt The child list iterator of the current Part..      
     * @param  mode The debugging mode of the current run.     
     * @param  containerLogId The unique uuid that the container logs will be related.     
     *      
     * @return boolean will the validation result be assigned or not?
     */
    protected boolean driveValidationResult(TestPart curPart,
            TestIterator innerIt,
            AtofEnumaration.DebuggingMode mode) {
        DriverFactory ourDriverFact = new DriverFactory();
        ExecutionDriver ourDriver = ourDriverFact.getInstance(mode);
        return ourDriver.validate(curPart,
                innerIt);
    }

    /**
     * @param  partResult boolean result of the last Test Part run operation.
     * @return  boolean will the iteration continue.
     *      
     */
    protected abstract boolean validateIfToContinue(boolean partResult);

    protected void validateTestCase(StepContainer refCur) {

        TestIterator curItemIter = refCur.createIter();
        curItemIter.init();
        ThreeDResultsMatrix ourMatrix = refCur.getResultMatrix();
        int invocationCountIndex = refCur.getInvocationCountIndex();

        while (curItemIter.isDone()) {

            curItemIter.next();
            TestPart currentPart = curItemIter.currentItem();
            int childIndex = refCur.getTestPartList().
                    indexOf(
                    currentPart);

            if (currentPart instanceof TestStep) {
                TestStep curStep = (TestStep) currentPart;
                if (!curStep.getTestDataList().isEmpty()) {


                    Map<Short, Pair<TestStepDataObject, Boolean>> resultSet =
                            curStep.getCaseResultSet();

                    Collection<Pair<TestStepDataObject, Boolean>> valueCollection =
                            resultSet.values();

                    int collSize = valueCollection.size();

                    Iterator<Pair<TestStepDataObject, Boolean>> valueIter =
                            valueCollection.iterator();

                    for (int u = 0; u < collSize; u++) {
                        boolean curValue = Tuple.get2(valueIter.next());
                        if (ourMatrix != null) {
                            ourMatrix.setResultIn3dArray(
                                    invocationCountIndex,
                                    childIndex,
                                    u,
                                    curValue);

                            Byte resByte = 1;
                            if (!curValue) {
                                resByte = 0;
                            }

                            String curStepName = curStep.getPartName();
                            // The name can be same as part Name because
                            // if we add Test Case Invocation Count and Data Count information to the
                            // name there will be too much confusion in the logs. Therefore I only
                            // add that kind of information in partGuidLong for uniqueness of logs
                            // for different invocations and data counts.

                            String curStepGuid = curStep.getPartGuid() + String.valueOf(
                                    invocationCountIndex)
                                    + String.valueOf(u + 1);
                            String curStepParentGuid = curStep.getParent().
                                    getPartGuid() + String.valueOf(
                                    invocationCountIndex)
                                    + String.valueOf(u + 1);

                            if (curStep.getParent().getContainerType()
                                    == AtofEnumaration.TestPartType.TestStepGroup) {

                                curStepGuid = curStep.getPartGuid() + String.valueOf(
                                        invocationCountIndex)
                                        + String.valueOf(u + 1) + curStep.getParent().
                                        getParent().getPartGuidLongFromChild().replaceAll(curStep.getParent().
                                        getParent().getPartGuid(),
                                        "");

                                curStepParentGuid = curStep.getParent().
                                        getPartGuid() + String.valueOf(
                                        invocationCountIndex)
                                        + String.valueOf(u + 1) + curStep.getParent().
                                        getParent().getPartGuidLongFromChild().
                                        replaceAll(curStep.getParent().
                                        getParent().getPartGuid(),
                                        "");
                            }
                            NotificationSubjectSingleton.getInstance().notifyLogHeaderMessages(
                                    Integer.parseInt(
                                    curStep.getPartId()),
                                    AtofEnumaration.TestPartType.TestStep.ordinal(),
                                    resByte,
                                    curStepParentGuid,
                                    curStepName,
                                    curStepGuid,
                                    ExecutionGuidProvider.createInstance().
                                    getExistingExecId());

                        }

                    }

                }

            }

        }

        refCur.resetCaseResults();
    }

    /**
     * This method is used to wait the results of the Asynch Test Steps at the end
     * of the Test Case before iteration to the next Test Case or to itself.
     *
     * @param  refCur The test steps under this container will be evaluated for 
     * execution.
     *           
     */
    protected void waitAsyncSteps(StepContainer refCur) {
        ThreeDResultsMatrix ourMatrix = refCur.getResultMatrix();
        TestIterator curItemIter = refCur.createIter();
        curItemIter.init();

        while (curItemIter.isDone()) {
            curItemIter.next();
            if (curItemIter.currentItem() instanceof TestStep) {
                TestStep curStep = (TestStep) curItemIter.currentItem();
                if (curStep.getDataArrayAppliesToCase()
                        && curStep.getAsynchCall()) {

                    Map<Short, Pair<TestStepDataObject, Boolean>> stepResSet =
                            curStep.getCaseResultSet();

                    Short caseIndex = refCur.getTestCaseDataIndex();

                    Pair<TestStepDataObject, Boolean> resSetCurPair = stepResSet.get(
                            caseIndex.shortValue());


                    if (resSetCurPair != null) {

                        TestStepDataObject asyncData = Tuple.get1(resSetCurPair);
                        TestStepResultType asyncResultType;
                        boolean asyncResult = false;

                        if (!asyncData.isConditionFailed()) {
                            if (!asyncData.isStopExecution()) {
                                TestStepResultDataObject result = TestStepExecutor.getResultReciever(false).recieve(
                                        asyncData.getMessageId(),
                                        curStep.getTimeOut() * 1000);


                                if (result != null) {
                                    asyncResultType = result.getResultType();
                                } else {
                                    asyncResultType = waitAsyncTimeOutResult(
                                            asyncData);
                                }

                                asyncResult = asyncResultType
                                        == asyncData.getExpectedResultType();
                            } else {
                                asyncResultType = TestStepResultType.FAILURE;
                                asyncResult = false;
                            }
                            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                                curStep.getPartName(),
                                AtofLogLevel.INFO,
                                TestStringConstants.getAsynchTestStepResultString()
                                + asyncResultType
                                + TestStringConstants.getExpectedResultString()
                                + asyncData.getExpectedResultType(),
                                asyncData.getMessageId());
                        }else{ //if (!asyncData.isConditionFailed()) {
                            asyncResultType =TestStepResultType.PASS;
                            asyncResult = true;
                        }

                        Byte resByte = 1;
                        if (!asyncResult) {
                            resByte = 0;
                        }
                        if(asyncData.isConditionFailed()){
                            resByte = 3;
                        }
                        int caseIndexInt = caseIndex - 1;
                        int intChildIndex =
                                refCur.getTestPartList().indexOf(curStep);

                        // I log the Asynch Test Step Result to the Resukt matrix
                        // here. Because I receive the result here.
                        ourMatrix.setResultIn3dArray(
                                refCur.getInvocationCountIndex(),
                                intChildIndex,
                                caseIndexInt,
                                asyncResult);

                        String curStepGuidLongPostFix = String.valueOf(refCur.getInvocationCountIndex()) + caseIndex.toString();

                        if (curStep.getParent().getContainerType()
                                == AtofEnumaration.TestPartType.TestStepGroup) {

                            curStepGuidLongPostFix += refCur.getParent().
                                    getPartGuidLongFromChild().replaceAll(refCur.getParent().
                                    getPartGuid(),
                                    "");

                        }

                        String dataObjName = TestStringConstants.getDataObjectPreFixString() + asyncData.getTestStepDataName()
                                + curStep.getTsgSpecificRetryCount();

                        NotificationSubjectSingleton.getInstance().notifyLogHeaderMessages(
                                Integer.parseInt(
                                asyncData.getTestStepDataId()),
                                AtofEnumaration.TestPartType.DataObject.ordinal(),
                                resByte,
                                curStep.getPartGuid() + curStepGuidLongPostFix,
                                dataObjName,
                                asyncData.getMessageId(),
                                ExecutionGuidProvider.createInstance().
                                getExistingExecId());



                        resSetCurPair =
                                new Pair<TestStepDataObject, Boolean>(asyncData,
                                asyncResult);

                        stepResSet.put(caseIndex.shortValue(),
                                resSetCurPair);

                        String caseSuccessLog = TestStringConstants.getExpectedString()
                                + refCur.getContainerType().toString()
                                + TestStringConstants.getSuccessPercString()
                                + refCur.getCaseSuccessPercentage();
                        NotificationSubjectSingleton.getInstance().notifyLogMessages(
                                curStep.getPartName(),
                                AtofLogLevel.INFO,
                                TestStringConstants.getAsyncTestStepSpecificResultSetString()
                                + stepResSet
                                + caseSuccessLog,
                                asyncData.getMessageId());
                    }

                }
            }

        }
    }

    /**
     * This method is used to wait for Timeout Result for Asynch Test Steps
     * at the end of the Test Case.
     * @param  asyncData TestStepDataObject that will be set for timeout operation.     
     *      
     * @return TestStepResultType returns the result as TestStepResultType
     */
    private TestStepResultType waitAsyncTimeOutResult(
            TestStepDataObject asyncData) {
        TestStepResultDataObject result = null;
        TestStepResultType stepResultType = TestStepResultType.NULL;

        asyncData.setAsynchCall(false);
        asyncData.setStopExecution(true);
        TestStepExecutor executor = new TestStepExecutor();
        executor.send(asyncData,
                false);
        short tryCount = 0;
        while (result == null && tryCount < 6) {
            result = TestStepExecutor.getResultReciever(true).recieve(
                    asyncData.getMessageId(),
                    5000);

            tryCount++;
        }
        asyncData.setAsynchCall(true);
        asyncData.setStopExecution(false);
        if (result == null) {
            stepResultType = TestStepResultType.TIMEOUT_UNHANDLED;
        } else {

            if (result.isCancelled()) {
                stepResultType = stepResultType.TIMEOUT;
            } else {
                stepResultType = result.getResultType();
            }
        }
        return stepResultType;

    }

    protected boolean finalLoggingforEasyContainers(
            boolean partResult) {

        String testPartParentGuid = "";
        String partName = testPartInRun.getPartName();
        String partGuid = testPartInRun.getPartGuid();

        try {

            testPartParentGuid = testPartInRun.getParent().getPartGuid();

        } catch (NullPointerException ee) {

            testPartParentGuid = "-";

        }


        Byte resByte = 1;

        if (testPartInRun.getTestPartList().isEmpty()) {
            NotificationSubjectSingleton.getInstance().notifyLogMessages(
                    partName,
                    AtofLogLevel.INFO,
                    TestStringConstants.getEmptyTestContainerString(),
                    partGuid);
            resByte = 2;
        } else {
            if (!testPartInRun.getFailedConditions().isEmpty()) {
                NotificationSubjectSingleton.getInstance().notifyLogMessages(
                        partName,
                        AtofLogLevel.INFO,
                        TestStringConstants.getConditionFailedString() + "\n"
                        + testPartInRun.getFailedConditions().toString(),
                        partGuid);
                resByte = 3; // log 3 when condition fails
            } else {
                if (!partResult) {
                    resByte = 0;
                }
            }
        }
        NotificationSubjectSingleton.getInstance().notifyLogHeaderMessages(
                Integer.parseInt(
                testPartInRun.getPartId()),
                testPartInRun.getContainerType().
                ordinal(),
                resByte,
                testPartParentGuid,
                partName,
                partGuid,
                ExecutionGuidProvider.createInstance().
                getExistingExecId());


        NotificationSubjectSingleton.getInstance().notifyLogMessages(
                partName,
                AtofLogLevel.INFO,
                TestStringConstants.getEndedExecutionString(),
                partGuid);
        return true;

    }
}
